﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TYBase;
using TYPLC.PLC;
using TYRTUBase;

namespace TYPLC.Omron
{
    public class OmronPLC_FinsTCP : IPLCBase<OmronAddressData>
    {
        ITYClient client;
        //这是一个串口,他需要串口参数
        public OmronPLC_FinsTCP(ITYClient rtu)
        {
            this.client = rtu;
        }

        public override bool Connect()
        {
            if (client.Connect() == false)
            {
                Log.I("连接失败...");
                return false;
            }
            if (CheckAddress("D100") == false)
            {
                Log.I("检查地址失败");
                return false;
            }
            return CheckHandShake();
        }



        public override bool AddressAs(string address, out OmronAddressData data)
        {
            return OmronPLCHelper.AddressAs(address, out data);
        }

        public override bool ReadPri(OmronAddressData address, out object value)
        { 
            var datas = OmronPLCHelper.FinsCmd(address, plcNode, pcNode, false);
            value = null;
            if (!SendAndWait(datas, out var dataBody))
            {
                return false;
            }
            switch (address.DataType)
            {
                case OmronDataType.Bit:
                    if (address.ReadLength <= 1)
                    {
                        value = (dataBody[0] == 1);
                        return true;
                    }
                    else
                    {
                        bool[] vals = new bool[address.ReadLength];
                        for (int i = 0; i < address.ReadLength; i++)
                        {
                            if (i >= dataBody.Length)
                            {
                                return false;
                            }
                            vals[i] = dataBody[i] == 1;
                        }
                        value = vals;
                        return true;
                    }
                    break;
                case OmronDataType.DWord:
                    if (address.ReadLength <= 1)
                    {
                        value = (dataBody[0] << 8) + dataBody[1];
                        return true;
                    }
                    else
                    {
                        int[] vals = new int[address.ReadLength];
                        for (int i = 0; i < address.ReadLength; i++)
                        {
                            if (i * 2 >= dataBody.Length)
                            {
                                return false;
                            }
                            vals[i] = (dataBody[i * 2] << 8) + dataBody[i * 2 + 1];
                        }
                        value = vals;
                        return true;
                    }
                    break;
                default:
                    break;
            }
            return false;


        }

        public override bool WritePri(OmronAddressData address, object value)
        {

            int[] intVals = value.ObjAsIntArray();
            if (intVals.Length != address.ReadLength)
                return false;

            byte[] arrayhead = OmronPLCHelper.FinsCmd(address, plcNode, pcNode, true);//前34字节和读指令基本一直，还需要拼接下面的输入数据数组
            byte[] array = null;
            switch (address.DataType)
            {
                case OmronDataType.Bit:

                    {
                        byte[] wdata = new byte[(int)(address.ReadLength)];
                        //转换写入值到wdata数组
                        for (int i = 0; i < address.ReadLength; i++)
                        {
                            wdata[i] = (byte)(intVals[i] % 256);//转换为PLC的高位在前储存方式
                        }
                        //拼接写入数组
                        array = new byte[(int)(address.ReadLength + 34)];
                        arrayhead.CopyTo(array, 0);
                        wdata.CopyTo(array, 34);
                    }
                    break;
                case OmronDataType.DWord:
                    {
                        byte[] wdata = new byte[(int)(address.ReadLength * 2)];
                        //转换写入值到wdata数组
                        for (int i = 0; i < address.ReadLength; i++)
                        {
                            wdata[i * 2 + 1] = (byte)(intVals[i] % 256);//转换为PLC的高位在前储存方式
                            wdata[i * 2] = (byte)(intVals[i] >> 8 % 256);
                        }
                        //拼接写入数组
                        array = new byte[(int)(address.ReadLength * 2 + 34)];
                        arrayhead.CopyTo(array, 0);
                        wdata.CopyTo(array, 34);
                    }
                    break;
                default:
                    break;
            }

            if (array == null)
                return false;
            return Send(array);
        }


        internal byte[] HandShake()
        {
            #region fins command
            byte[] array = new byte[20];
            array[0] = 0x46;
            array[1] = 0x49;
            array[2] = 0x4E;
            array[3] = 0x53;

            array[4] = 0;
            array[5] = 0;
            array[6] = 0;
            array[7] = 0x0C;

            array[8] = 0;
            array[9] = 0;
            array[10] = 0;
            array[11] = 0;

            array[12] = 0;
            array[13] = 0;
            array[14] = 0;
            array[15] = 0;//ERR？

            array[16] = 0;
            array[17] = 0;
            array[18] = 0;
            array[19] = 0;//TODO:ask for client and server node number, the client node will allocated automatically
            //array[19] = this.GetIPNode(lIP);//本机IP地址的末位
            #endregion fins command
            return array;
        }

        private bool CheckHandShake()
        {
            var sendData = HandShake();
            if (!SendAndWait(sendData, 24, out var buffer))
            {
                Log.I("send CheckHandShake fail");
                return false;
            }
            //46494e53000000100000000100000000000000fc00000002
            if (buffer[15] != 0)//TODO:这里的15号是不是ERR信息暂时不能完全肯定
            {
                Log.I("send CheckHandShake fail buffer 15 error");
                return false;
            }
            else
            {
                pcNode = buffer[19];
                plcNode = buffer[23];
                Log.I($"send CheckHandShake ok pcNode:{pcNode} plcNode:{plcNode}");
                return true;
            }
        }

        private object objLock = new object();
        private byte pcNode;
        private byte plcNode;

        private bool SendAndWait(byte[] sendStr, out byte[] dataRead)
        {

            dataRead = null;
            lock (objLock)
            {
                client.ClearInBuff();
                client.Write(sendStr);
                Stopwatch sw = Stopwatch.StartNew();
                while (sw.ElapsedMilliseconds < 1 * 1000)
                {
                    Thread.Sleep(1);
                    if (client.Read(out var receData) > 0)
                    {
                        dataRead = dataRead.Append(receData);
                        if (CheckReceive(dataRead, out dataRead))
                        {
                            return true;
                        }
                    }
                }
            }
            Log.I("omron outtime");
            Log.I("omron send:" + sendStr);
            Log.I("omron rece:" + dataRead.AsString());
            return false;
        }


        private bool SendAndWait(byte[] sendStr, int len, out byte[] dataRead)
        {

            dataRead = null;
            lock (objLock)
            {
                client.ClearInBuff();
                client.Write(sendStr);
                Log.I($"orm plc :send : {sendStr.AsHexString()}");
                Stopwatch sw = Stopwatch.StartNew();
                while (sw.ElapsedMilliseconds < 1 * 1000)
                {
                    Thread.Sleep(1);
                    if (client.Read(out var receData) > 0)
                    {
                        Log.I($"orm plc :receive : {receData.AsHexString()}");
                        dataRead = dataRead.Append(receData);
                        if (dataRead.Length >= len)
                        {
                            return true;
                        }
                    }
                }
            }
            Log.I("omron outtime");
            Log.I("omron send:" + sendStr.AsHexString());
            Log.I("omron rece:" + dataRead.AsHexString());
            return false;
        }


        private bool Send(byte[] sendStr)
        {

            byte[] dataRead = null;
            return SendAndWait(sendStr, out dataRead);
        }


        private bool CheckReceive(byte[] data, out byte[] dataBody)
        {
            //头---内容
            //send: 46494e530000001a000000020000000080000200020000fc00ff0101300096000001
            //receive: 46494e53000000170000000200000000c0000200fc00000200ff0101000000
            dataBody = null;
            if (data == null || data.Length < 8)
            {
                Log.I("ormon plc :if (data == null || data.Length < 8)");
                return false;
            }
            int fsrLen = (data[6] << 8) + (data[7]);
            //46494e5300 0000080000 0003000000 03
            if (data.Length - 8 != fsrLen)
            {
                return false;
            }
            if (data.Length < 16)
            {
                return false;
            }
            if (data[11] == 3)
                if (OmronPLCHelper.CheckHeadError(data[15]) == false)
                {
                    return false;
                }
            if (fsrLen >= 22)
            {
                dataBody = data.Sub(30);
                if (dataBody.Length != fsrLen - 22)
                {
                    Log.I($"ormon plc :if (dataBody.Length {dataBody.Length} != fsrLen {fsrLen})");
                    return false;
                }
                return true;
            }
            return true;

        }

    }
}
